use crate::{
    data::{Command, CommandType},
    error::InvalidInstructionError,
};

use super::asm::{AsmConditionalJmp, AsmGenerator, AsmRegistor};

pub(super) struct Goto {
    label: String,
    current_function_label: Option<String>,
    is_if_bigger_zero: bool,
}

impl TryFrom<&Command> for Goto {
    type Error = InvalidInstructionError;
    /// parse function from string
    fn try_from(value: &Command) -> Result<Self, Self::Error> {
        if ![CommandType::Goto, CommandType::IfGoto].contains(&value.command_type) {
            return Err(InvalidInstructionError::Command(
                "neither a goto or if-goto.".into(),
            ));
        }
        Ok(Self {
            label: value.arg1.clone(),
            current_function_label: None,
            is_if_bigger_zero: value.command_type == CommandType::IfGoto,
        })
    }
}
impl Goto {
    pub(super) fn set_current_function_label(mut self, label: String) -> Self {
        self.current_function_label = Some(label);
        self
    }
    /// convert to asm code
    pub(super) fn to_asm(&self) -> String {
        if self.is_if_bigger_zero {
            self.asm_goto_if_biggzero()
        } else {
            self.asm_goto()
        }
    }
    fn get_label(&self) -> String {
        if self.current_function_label.is_none() {
            panic!("don't set current function lable.");
        }
        format!(
            "{}${}",
            self.current_function_label.as_ref().unwrap(),
            self.label
        )
    }

    fn asm_goto(&self) -> String {
        let cache = [
            AsmGenerator::asm_addr(&self.get_label()),
            AsmGenerator::asm_jmp().into(),
        ];
        cache.join("\n")
    }

    fn asm_goto_if_biggzero(&self) -> String {
        let cache: Vec<String> = vec![
            // read pop the topmost value to reg D from stack
            AsmGenerator::asm_addr_sp(), //"@SP".into(),
            AsmGenerator::asm_assignment(
                &format!("{}{}", AsmGenerator::AREG, AsmGenerator::MREG),
                &AsmGenerator::asm_arithmetic_sub(AsmGenerator::MREG, "1"),
            ), //"AM=M-1".into(),
            AsmGenerator::asm_assign_m_to_d(), //"D=M".into(),
            AsmGenerator::asm_addr(&self.get_label()), //format!("@{}", self.get_label()),
            // jump if the reg D(temp0) bigger than 0 .
            AsmGenerator::asm_conditional_jmp(AsmRegistor::D, AsmConditionalJmp::Jne), //"D;JNE".into(),
        ];
        cache.join("\n")
    }
}
